/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.manage;

import cn.ac.nercis.pes.common.utils.PasswordUtils;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.common.ResultCode;
import cn.ac.nercis.pes.model.dto.manage.ModifyPwdDTO;
import cn.ac.nercis.pes.model.dto.manage.UserDTO;
import cn.ac.nercis.pes.model.dto.manage.UserDetailDTO;
import cn.ac.nercis.pes.repository.dal.manage.RoleRepository;
import cn.ac.nercis.pes.repository.dal.manage.UserRepository;
import cn.ac.nercis.pes.repository.mapper.manage.UserMapper;
import cn.ac.nercis.pes.repository.model.manage.RoleDO;
import cn.ac.nercis.pes.repository.model.manage.UserDO;
import cn.ac.nercis.pes.service.config.ServiceConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用户管理服务
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public class UserService {
    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final ServiceConfig serviceConfig;

    private static final List<String> PRESET_USERS = new ArrayList<>();

    @Autowired
    public UserService(UserRepository userRepository, RoleRepository roleRepository,
                       ServiceConfig serviceConfig) {
        this.userRepository = userRepository;
        this.roleRepository = roleRepository;
        this.serviceConfig = serviceConfig;
    }

    public List<String> findPresetUsers(){
        if(CollectionUtils.isEmpty(PRESET_USERS)){
            PRESET_USERS.addAll(Stream.of("admin")
                    .map(userRepository::findByUsername)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .map(UserDO::getId)
                    .toList());
        }
        return PRESET_USERS;
    }

    public List<UserDTO> findAll(){
        return this.userRepository.findAll().stream()
                .map(UserMapper.INSTANCE::toDTO)
                .toList();
    }

    public Page<UserDTO> findAll(String username, String displayName, Pageable pageable){
        UserDO example = UserDO.builder().username(username).displayName(displayName).build();
        ExampleMatcher matcher = ExampleMatcher.matchingAny()
                .withIgnorePaths("activated")
                .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.contains())
                .withMatcher("displayName",ExampleMatcher.GenericPropertyMatchers.contains());
       return this.userRepository.findAll(Example.of(example,matcher),pageable)
               .map(UserMapper.INSTANCE::toDTO);
    }

    public Result<UserDTO> save(UserDetailDTO userDetail){
        try {
            if (StringUtils.isEmpty(userDetail.getId())) {
                return create(userDetail);
            } else {
                return modify(userDetail);
            }
        }catch (Exception e){
            log.error("保存用户出错：",e);
            return Result.failed(ResultCode.FAIL);
        }
    }

    public Result<String> modifyPassword(ModifyPwdDTO modifyPwd){
        UserDO userDO = this.userRepository.findById(modifyPwd.getId()).orElse(null);
        if(Objects.isNull(userDO)){
            return Result.failed(ResultCode.USER_NOT_EXIST);
        }
        if(Objects.nonNull(userDO.getWebServer()) && userDO.getWebServer()){
            return Result.failed("当前用户为服务端用户不允许修改密码");
        }
        if(!StringUtils.equals(userDO.getPassword(), PasswordUtils.encryption(modifyPwd.getOldPwd()))){
            return Result.failed(ResultCode.USER_PWD_ERROR);
        }
        return Optional.of(userDO)
                .map(u->{
                    u.setPassword(PasswordUtils.encryption(modifyPwd.getNewPwd()));
                    u.setModifyTime(LocalDateTime.now());
                    return u;
                })
                .map(userRepository::save)
                .map(UserDO::getId)
                .map(Result::success)
                .orElse(Result.failed());
    }

    public Result<String> delete(final String id){
        if(findPresetUsers().contains(id)){
            return Result.failed(ResultCode.USER_NOT_ALLOW_DELETE);
        }
        try {
            return userRepository.findById(id)
                    .map(userDO -> {
                        userDO.setRoles(Collections.emptySet());
                        return userRepository.save(userDO);
                    })
                    .map(userDO -> {
                        userRepository.delete(userDO);
                        return userDO;
                    })
                    .map(userDO -> Result.success(userDO.getId()))
                    .orElse(Result.failed(ResultCode.FAIL));
        }catch (Exception ex){
            log.error("删除用户出错：",ex);
            return Result.failed(ResultCode.FAIL);
        }
    }

    private Result<UserDTO> create(UserDetailDTO userDetail){
        if(userRepository.existsByUsername(userDetail.getUsername())){
            return Result.failed(ResultCode.USER_NAME_EXIST);
        }
        Optional<RoleDO> roleDO = this.roleRepository.findById(userDetail.getRoleId());
        if(roleDO.isEmpty()){
            return Result.failed(ResultCode.ROLE_NOT_EXIST);
        }
        UserDO userDO = UserMapper.INSTANCE.toDO(userDetail);
        userDO.setPassword(PasswordUtils.encryption(serviceConfig.getDefaultPwd()));
        userDO.setActivated(true);
        userDO.getRoles().add(roleDO.get());
        userDO.setWebServer(false);
        userDO.setCreateTime(LocalDateTime.now());
        return Optional.of(userRepository.save(userDO))
                .map(UserMapper.INSTANCE::toDTO)
                .map(Result::success)
                .orElse(Result.failed(ResultCode.FAIL));
    }

    private Result<UserDTO> modify(UserDetailDTO userDetail){
        UserDO userDO = this.userRepository.findById(userDetail.getId()).orElse(null);
        if(Objects.isNull(userDO)){
            return Result.failed(ResultCode.USER_NOT_EXIST);
        }
        Optional<RoleDO> roleDO = this.roleRepository.findById(userDetail.getRoleId());
        if(roleDO.isEmpty()){
            return Result.failed(ResultCode.ROLE_NOT_EXIST);
        }
        userDO.setDisplayName(userDetail.getDisplayName());
        userDO.setPhone(userDetail.getPhone());
        userDO.setEmail(userDetail.getEmail());
        userDO.setRoles(SetUtils.hashSet(roleDO.get()));
        userDO.setModifyTime(LocalDateTime.now());
        return Optional.of(userRepository.save(userDO))
                .map(UserMapper.INSTANCE::toDTO)
                .map(Result::success)
                .orElse(Result.failed(ResultCode.FAIL));
    }
}